import { Expression, BoolItem, BoolOperation, Operator, EMPTY } from './types';
import { isBoolExpression, makeSureUUID, buildBoolItemMap } from './util';
import lodash from 'lodash';
import { reverse } from './not';

/**
 * 笛卡尔展开多个集合
 *
 * @param {string[][][]} idMatrix - 集合的集合
 * @return {string[][]} 展开后的集合
 */
export function decareCollection(idMatrix: string[][][]): string[][] {
  if (idMatrix.length === 0) {
    return [];
  }

  if (idMatrix.length === 1) {
    return idMatrix[0];
  }

  const prefixList = idMatrix.shift();
  const suffixList = decareCollection(idMatrix);
  const rst: string[][] = [];

  (prefixList as string[][]).forEach((prefix) => {
    suffixList.forEach((sufix) => {
      rst.push([...prefix, ...sufix]);
    });
  });

  return rst;
}

function expandImp(exp: Expression, prefix?: string[][]): string[][] {
  if (isBoolExpression(exp)) {
    prefix = prefix || [];

    if (exp.operator === BoolOperation.AND) {
      // 叉乘
      const suffixMatrix: string[][][] = exp.args.map((item) => expandImp(item));

      const suffixDecare = decareCollection(suffixMatrix);

      if (prefix && prefix.length > 0) {
        return decareCollection([prefix, suffixDecare]);
      }
      return suffixDecare;
    } else {
      // OR
      const suffixMatrix: string[][][] = exp.args.map((item) => expandImp(item, prefix));

      return Array.prototype.concat.apply([], suffixMatrix);
    }
  } else {
    if (prefix && prefix.length > 0) {
      return prefix.map((pre) => [...pre, exp.uuid as string]);
    } else {
      return [[exp.uuid as string]];
    }
  }
}


function intersection$(expA: BoolItem, expB: BoolItem): BoolItem {
  const { cloneDeep: clone } = lodash;

  // key不同的，交集未空集
  if (expA.key !== expB.key) {
    return EMPTY;
  }

  if (expA.operator > expB.operator) {
    [expB, expA] = [expA, expB];
  }

  const { operator: opA, value: vA } = expA;
  const { operator: opB, value: vB } = expB;

  // 任意集合和全集的交集是原集合
  if (opB === Operator.FULL) {
    return expA;
  }

  // 任意集合和空集的交集是空集
  if (opA === Operator.EMPTY || opB === Operator.EMPTY) {
    return EMPTY;
  }

  if (opA === Operator.SEGMENTS_INNER) {
    if (Array.isArray(vA)) {
      const values = (vA as BoolItem[]).map((item: BoolItem) => intersectionItem(item, expB));
      const composed: BoolItem[] = [];

      values.forEach((item: BoolItem) => {
        if (item.operator === Operator.EMPTY) {
          return;
        }

        if (item.operator === Operator.SEGMENTS_INNER) {
          composed.push(...(item.value as BoolItem[]));
        } else {
          composed.push(item);
        }
      });
      return {
        key: expA.key,
        operator: Operator.SEGMENTS_INNER,
        value: composed,
      };
    } else {
      return EMPTY;
    }
  }

  if (opB === Operator.SEGMENTS_INNER) {
    if (Array.isArray(vB)) {
      const values = (vB as BoolItem[]).map((item: BoolItem) => intersectionItem(expA, item));
      const composed: BoolItem[] = [];

      values.forEach((item: BoolItem) => {
        if (item.operator === Operator.EMPTY) {
          return;
        }

        if (item.operator === Operator.SEGMENTS_INNER) {
          composed.push(...(item.value as BoolItem[]));
        } else {
          composed.push(item);
        }
      });
      return {
        key: expA.key,
        operator: Operator.SEGMENTS_INNER,
        value: composed,
      };
    } else {
      return EMPTY;
    }
  }

  if (opA === Operator.EQ) {
    switch (opB) {
      case Operator.EQ:
        return vA === vB ? clone(expA) : EMPTY;
      case Operator.GT:
        return vA > vB ? clone(expA) : EMPTY;
      case Operator.LT:
        return vA < vB ? clone(expA) : EMPTY;
      case Operator.NGT:
        return vA <= vB ? clone(expA) : EMPTY;
      case Operator.NLT:
        return vA >= vB ? clone(expA) : EMPTY;
      case Operator.NEQ:
        return vA !== vB ? clone(expA) : EMPTY;
      case Operator.IN:
        return Array.isArray(vB) && (vB as Array<typeof vA>).includes(vA) ? clone(expA) : EMPTY;
      case Operator.NOT_IN:
        return Array.isArray(vB) && !(vB as Array<typeof vA>).includes(vA) ? clone(expA) : EMPTY;
      case Operator.RIN:
        return Array.isArray(vB) && vB.length > 1 && vA <= vB[1] && vA >= vB[0]
          ? clone(expA)
          : EMPTY;
      case Operator.LO_IN_INNER:
        return Array.isArray(vB) && vB.length > 1 && vA <= vB[1] && vA > vB[0] ? clone(expA) : EMPTY;
      case Operator.RO_IN_INNER:
        return Array.isArray(vB) && vB.length > 1 && vA < vB[1] && vA >= vB[0] ? clone(expA) : EMPTY;
      case Operator.O_IN_INNER:
        return Array.isArray(vB) && vB.length > 1 && vA < vB[1] && vA > vB[0] ? clone(expA) : EMPTY;
      default:
        return EMPTY;
    }
  } else if (opA === Operator.NEQ) {
    switch (opB) {
      case Operator.NEQ:
        if (vA === vB) {
          return clone(expA);
        } else {
          const min = Math.min(vA as number, vB as number);
          const max = Math.max(vA as number, vB as number);
          return {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: [
              {
                key: expA.key,
                operator: Operator.LT,
                value: min as number,
              },
              {
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [min as number, max as number],
              },
              {
                key: expA.key,
                operator: Operator.GT,
                value: max as number,
              },
            ],
          };
        }
      case Operator.IN:
        if (Array.isArray(vB)) {
          if ((vB as Array<typeof vA>).includes(vA)) {
            const rst = clone(expB);
            const vr = rst.value as Array<typeof vA>;
            vr.splice(vr.indexOf(vA), 1);

            if (vr.length === 0) {
              return EMPTY;
            }

            return rst;
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.NOT_IN:
        if (!Array.isArray(vB)) {
          return EMPTY;
        }
        {
          const rst = clone(expB);
          const rstV = clone(expB.value) as Array<number>;
          rstV.push(vA as number);
          rst.value = lodash.sortBy(lodash.uniq(rstV));
          return rst;
        }
      case Operator.LT:
        if (vA >= vB) {
          return clone(expB);
        } else {
          const rst: BoolItem = {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: [
              {
                key: expA.key,
                operator: Operator.LT,
                value: vA,
              },
              {
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [vA as number, vB as number],
              },
            ],
          };
          return rst;
        }
      case Operator.GT:
        if (vA <= vB) {
          return clone(expB);
        } else {
          const rst: BoolItem = {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: [
              {
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [vB as number, vA as number],
              },
              {
                key: expA.key,
                operator: Operator.GT,
                value: vA,
              },
            ],
          };
          return rst;
        }
      case Operator.NGT:
        if (vA > vB) {
          return clone(expB);
        } else if (vA === vB) {
          const rst = clone(expB);
          rst.operator = Operator.LT;
          return rst;
        } else {
          const rst: BoolItem = {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: [
              {
                key: expA.key,
                operator: Operator.LT,
                value: vA,
              },
              {
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [vA as number, vB as number],
              },
            ],
          };
          return rst;
        }
      case Operator.NLT:
        if (vA < vB) {
          return clone(expB);
        } else if (vA === vB) {
          const rst = clone(expB);
          rst.operator = Operator.GT;
          return rst;
        } else {
          const rst: BoolItem = {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: [
              {
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [vB as number, vA as number],
              },
              {
                key: expA.key,
                operator: Operator.GT,
                value: vA,
              },
            ],
          };
          return rst;
        }
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;

          if (vA < left || vA > vB) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.LO_IN_INNER;
            return rst;
          } else if (vA === right) {
            const rst = clone(expB);
            rst.operator = Operator.RO_IN_INNER;
            return rst;
          } else {
            const rst: BoolItem = {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: [
                {
                  key: expA.key,
                  operator: Operator.RO_IN_INNER,
                  value: [left as number, vA as number],
                },
                {
                  key: expA.key,
                  operator: Operator.LO_IN_INNER,
                  value: [vA as number, right as number],
                },
              ],
            };

            return rst;
          }
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;

          if (vA <= left || vA > vB) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.LO_IN_INNER;
            return rst;
          } else if (vA === right) {
            const rst = clone(expB);
            rst.operator = Operator.RO_IN_INNER;
            return rst;
          } else {
            const rst: BoolItem = {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: [
                {
                  key: expA.key,
                  operator: Operator.O_IN_INNER,
                  value: [left as number, vA as number],
                },
                {
                  key: expA.key,
                  operator: Operator.LO_IN_INNER,
                  value: [vA as number, right as number],
                },
              ],
            };

            return rst;
          }
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;

          if (vA < left || vA >= vB) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.LO_IN_INNER;
            return rst;
          } else if (vA === right) {
            const rst = clone(expB);
            rst.operator = Operator.RO_IN_INNER;
            return rst;
          } else {
            const rst: BoolItem = {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: [
                {
                  key: expA.key,
                  operator: Operator.RO_IN_INNER,
                  value: [left as number, vA as number],
                },
                {
                  key: expA.key,
                  operator: Operator.O_IN_INNER,
                  value: [vA as number, right as number],
                },
              ],
            };

            return rst;
          }
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;

          if (vA <= left || vA >= vB) {
            return clone(expB);
          } else if (vA === left || vA === right) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else {
            const rst: BoolItem = {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: [
                {
                  key: expA.key,
                  operator: Operator.O_IN_INNER,
                  value: [left as number, vA as number],
                },
                {
                  key: expA.key,
                  operator: Operator.O_IN_INNER,
                  value: [vA as number, right as number],
                },
              ],
            };

            return rst;
          }
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.IN) {
    if (!Array.isArray(vA)) {
        return EMPTY;
    }

    switch (opB) {
      case Operator.IN:
        if (Array.isArray(vB)) {
          const rst = clone(expA);
          rst.value = lodash.intersection(vA as number[], vB as number[]);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      case Operator.NOT_IN:
        if (Array.isArray(vB)) {
          const rst = clone(expA);
          rst.value = lodash.difference(vA as number[], vB as number[]);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      case Operator.LT: {
        const rst = clone(expA);
        rst.value = (vA as number[]).filter((i: number) => i < vB);
        if (rst.value.length === 0) {
          return EMPTY;
        }
        return rst;
      }
      case Operator.GT: {
        const rst = clone(expA);
        rst.value = (vA as number[]).filter((i) => i > vB);
        if (rst.value.length === 0) {
          return EMPTY;
        }
        return rst;
      }
      case Operator.NGT: {
        const rst = clone(expA);
        rst.value = (vA as number[]).filter((i) => i <= vB);
        if (rst.value.length === 0) {
          return EMPTY;
        }
        return rst;
      }
      case Operator.NLT: {
        const rst = clone(expA);
        rst.value = (vA as number[]).filter((i) => i >= vB);
        if (rst.value.length === 0) {
          return EMPTY;
        }
        return rst;
      }
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const rst = clone(expA);
          rst.value = (vA as number[]).filter((i) => i >= left && i <= right);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const rst = clone(expA);
          rst.value = (vA as number[]).filter((i) => i > left && i <= right);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const rst = clone(expA);
          rst.value = (vA as number[]).filter((i) => i >= left && i < right);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const rst = clone(expA);
          rst.value = (vA as number[]).filter((i) => i > left && i < right);
          if (rst.value.length === 0) {
            return EMPTY;
          }
          return rst;
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.NOT_IN) {
    if (!Array.isArray(vA) || vA.length < 1) {
      return EMPTY;
    }

    switch (opB) {
      case Operator.NOT_IN: {
        if (Array.isArray(vB) && vB.length > 0) {
          const rst = clone(expA);
          rst.value = lodash.sortBy(lodash.union(vA as number[], vB as number[]));
          return rst;
        } else {
          return EMPTY;
        }
      }
      case Operator.LT: {
        const min = Math.min(...(vA as number[]));
        // const max = Math.max(...(vA as number[]));
        if (vB <= min) {
          return clone(expB);
        } else {
          const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i <= vB));
          const rstValues: BoolItem[] = [];
          let lastPort: number | null = null;

          segPart.forEach((point: number) => {
            if (lastPort == null) {
              rstValues.push({
                key: expA.key,
                operator: Operator.LT,
                value: point,
              });
            } else {
              rstValues.push({
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [lastPort, point],
              });
            }
            lastPort = point;
          });

          if (lastPort !== null && lastPort < vB) {
            rstValues.push({
              key: expA.key,
              operator: Operator.O_IN_INNER,
              value: [lastPort, vB as number],
            });
          }

          return {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: rstValues,
          };
        }
      }
      case Operator.GT: {
        const max = Math.max(...(vA as number[]));
        if (vB >= max) {
          return clone(expB);
        } else {
          const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i > vB));
          const rstValues: BoolItem[] = [];
          let lastPort = vB as number;

          segPart.forEach((point: number) => {
            rstValues.push({
              key: expA.key,
              operator: Operator.O_IN_INNER,
              value: [lastPort, point],
            });
            lastPort = point;
          });

          rstValues.push({
            key: expA.key,
            operator: Operator.GT,
            value: lastPort,
          });

          return {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: rstValues,
          };
        }
      }
      case Operator.NGT: {
        const min = Math.min(...(vA as number[]));

        if (vB < min) {
          return clone(expB);
        } else if (vB === min) {
          const rst = clone(expB);
          rst.operator = Operator.LT;
          return rst;
        } else {
          const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i <= vB));
          const rstValues: BoolItem[] = [];
          let lastPort: number | null = null;

          segPart.forEach((point: number) => {
            if (lastPort == null) {
              rstValues.push({
                key: expA.key,
                operator: Operator.LT,
                value: point,
              });
            } else {
              rstValues.push({
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [lastPort, point],
              });
            }

            lastPort = point;
          });

          if (lastPort !== null && lastPort < vB) {
            rstValues.push({
              key: expA.key,
              operator: Operator.LO_IN_INNER,
              value: [lastPort, vB as number],
            });
          }

          return {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: rstValues,
          };
        }
      }
      case Operator.NLT: {
        const max = Math.max(...(vA as number[]));
        if (vB > max) {
          return clone(expB);
        } else if (vB === max) {
          const rst = clone(expB);
          rst.operator = Operator.GT;
          return rst;
        } else {
          const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i >= vB));
          const rstValues: BoolItem[] = [];
          let lastPort = vB as number;
          let closeLeftEnding = lastPort !== segPart[0];

          segPart.forEach((point: number) => {
            if (point > lastPort) {
              rstValues.push({
                key: expA.key,
                operator: closeLeftEnding ? Operator.RO_IN_INNER : Operator.O_IN_INNER,
                value: [lastPort, point],
              });
              closeLeftEnding = false;
            }
            lastPort = point;
          });

          rstValues.push({
            key: expA.key,
            operator: Operator.GT,
            value: lastPort,
          });

          return {
            key: expA.key,
            operator: Operator.SEGMENTS_INNER,
            value: rstValues,
          };
        }
      }
      case Operator.RIN: {
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const min = Math.min(...(vA as number[]));
          const max = Math.max(...(vA as number[]));

          if (right < min || left > max) {
            return clone(expB);
          } else if (right === min) {
            const rst = clone(expB);
            rst.operator = Operator.RO_IN_INNER;
            return rst;
          } else if (left === max) {
            const rst = clone(expB);
            rst.operator = Operator.LO_IN_INNER;
            return rst;
          } else {
            const segPart: number[] = lodash.sortBy((vA as number[])
              .filter((i) => i >= left && i <= right));
            const rstValues: BoolItem[] = [];
            let lastPort = left as number;
            let closeLeftEnding = lastPort !== segPart[0];

            segPart.forEach((point: number) => {
              if (point > left) {
                rstValues.push({
                  key: expA.key,
                  operator: closeLeftEnding ? Operator.RO_IN_INNER : Operator.O_IN_INNER,
                  value: [lastPort, point],
                });

                closeLeftEnding = false;
              }

              lastPort = point;
            });

            if (lastPort < right) {
              rstValues.push({
                key: expA.key,
                operator: Operator.LO_IN_INNER,
                value: [lastPort, right as number],
              });
            }

            if (rstValues.length === 1) {
              return rstValues[0];
            }

            return {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: rstValues,
            };
          }
        } else {
          return EMPTY;
        }
      }
      case Operator.LO_IN_INNER: {
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const min = Math.min(...(vA as number[]));
          const max = Math.max(...(vA as number[]));

          if (right < min || left >= max) {
            return clone(expB);
          } else if (right === min) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else {
            const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i > left && i <= right));
            const rstValues: BoolItem[] = [];
            let lastPort = left as number;

            segPart.forEach((point: number) => {
              rstValues.push({
                key: expA.key,
                operator: Operator.O_IN_INNER,
                value: [lastPort, point],
              });

              lastPort = point;
            });

            if (lastPort < right) {
              rstValues.push({
                key: expA.key,
                operator: Operator.LO_IN_INNER,
                value: [lastPort, right as number],
              });
            }

            if (rstValues.length === 1) {
              return rstValues[0];
            }

            return {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: rstValues,
            };
          }
        } else {
          return EMPTY;
        }
      }
      case Operator.RO_IN_INNER: {
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const min = Math.min(...(vA as number[]));
          const max = Math.max(...(vA as number[]));

          if (right <= min || left > max) {
            return clone(expB);
          } else if (left === max) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else {
            const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i >= left && i < right));
            const rstValues: BoolItem[] = [];
            let lastPort = left as number;
            let closeLeftEnding = lastPort !== segPart[0];

            segPart.forEach((point: number) => {
              if (point > left) {
                rstValues.push({
                  key: expA.key,
                  operator: closeLeftEnding ? Operator.RO_IN_INNER : Operator.O_IN_INNER,
                  value: [lastPort, point],
                });

                closeLeftEnding = false;
              }

              lastPort = point;
            });

            rstValues.push({
              key: expA.key,
              operator: closeLeftEnding ? Operator.RO_IN_INNER : Operator.O_IN_INNER,
              value: [lastPort, right as number],
            });

            if (rstValues.length === 1) {
              return rstValues[0];
            }

            return {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: rstValues,
            };
          }
        } else {
          return EMPTY;
        }
      }
      case Operator.O_IN_INNER: {
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          const min = Math.min(...(vA as number[]));
          const max = Math.max(...(vA as number[]));

          if (right <= min || left >= max) {
            return clone(expB);
          } else {
            const segPart: number[] = lodash.sortBy((vA as number[]).filter((i) => i > left && i < right));
            const rstValues: BoolItem[] = [];
            let lastPort = left as number;

            segPart.forEach((point: number) => {
              if (point > left) {
                rstValues.push({
                  key: expA.key,
                  operator: Operator.O_IN_INNER,
                  value: [lastPort, point],
                });
              }

              lastPort = point;
            });

            rstValues.push({
              key: expA.key,
              operator: Operator.O_IN_INNER,
              value: [lastPort, right as number],
            });

            if (rstValues.length === 1) {
              return rstValues[0];
            }

            return {
              key: expA.key,
              operator: Operator.SEGMENTS_INNER,
              value: rstValues,
            };
          }
        } else {
          return EMPTY;
        }
      }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.LT) {
    switch (opB) {
      case Operator.LT:
        return vA < vB ? clone(expA) : clone(expB);
      case Operator.GT:
        if (vA <= vB) {
          return EMPTY;
        } else {
          return {
            operator: Operator.O_IN_INNER,
            key: expA.key,
            value: [vB as number, vA as number],
          };
        }
      case Operator.NGT:
        return vA <= vB ? clone(expA) : clone(expB);
      case Operator.NLT:
        if (vA <= vB) {
          return EMPTY;
        } else {
          return {
            operator: Operator.RO_IN_INNER,
            key: expA.key,
            value: [vB as number, vA as number],
          };
        }
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA <= right) {
            return {
              operator: Operator.RO_IN_INNER,
              key: expA.key,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.O_IN_INNER,
              key: expA.key,
              value: [left as number, vA as number],
            };
          } else if (vA === right) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.RO_IN_INNER,
              key: expA.key,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.O_IN_INNER,
              key: expA.key,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.GT) {
    switch (opB) {
      case Operator.GT:
        return vA < vB ? clone(expB) : clone(expA);
      case Operator.NGT:
        if (vA >= vB) {
          return EMPTY;
        } else {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [vA as number, vB as number],
          };
        }
      case Operator.NLT:
        return vA >= vB ? clone(expA) : clone(expB);
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA < left) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.LO_IN_INNER;
            return rst;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.LO_IN_INNER,
              key: expA.key,
              value: [vA as number, right as number],
            };
          } else {
            return EMPTY;
          }
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA < left) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.LO_IN_INNER,
              key: expA.key,
              value: [vA as number, right as number],
            };
          } else {
            return EMPTY;
          }
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA < left) {
            return clone(expB);
          } else if (vA === left) {
            const rst = clone(expB);
            rst.operator = Operator.O_IN_INNER;
            return rst;
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.O_IN_INNER,
              key: expA.key,
              value: [vA as number, right as number],
            };
          } else {
            return EMPTY;
          }
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return clone(expB);
          } else if (vA > left && vA < right) {
            return {
              operator: Operator.O_IN_INNER,
              key: expA.key,
              value: [vA as number, right as number],
            };
          } else {
            return EMPTY;
          }
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.NGT) {
    // lt or eq
    switch (opB) {
      case Operator.NGT:
        return vA <= vB ? clone(expA) : clone(expB);
      case Operator.NLT:
        if (vA < vB) {
          return EMPTY;
        }
        if (vA === vB) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: vA,
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [vB as number, vA as number],
          };
        }
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA < left) {
            return EMPTY;
          }
          if (vA === left) {
            return {
              key: expA.key,
              operator: Operator.EQ,
              value: vA,
            };
          } else if (vA > left && vA <= right) {
            return {
              key: expA.key,
              operator: Operator.RIN,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.LO_IN_INNER,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA < left) {
            return EMPTY;
          } else if (vA === left) {
            return {
              key: expA.key,
              operator: Operator.EQ,
              value: vA,
            };
          } else if (vA > left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.RIN,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA <= left) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.O_IN_INNER,
              value: [left as number, vA as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.NLT) {
    // gt or eq
    switch (opB) {
      case Operator.NLT:
        return vA <= vB ? clone(expB) : clone(expA);
      case Operator.RIN:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA > right) {
            return EMPTY;
          } else if (vA === right) {
            const rst = clone(expA);
            rst.operator = Operator.EQ;
            return rst;
          } else if (vA >= left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.RIN,
              value: [vA as number, right as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.LO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA > right) {
            return EMPTY;
          } else if (vA === right) {
            const rst = clone(expA);
            rst.operator = Operator.EQ;
            return rst;
          } else if (vA > left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.RIN,
              value: [vA as number, right as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.RO_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA >= right) {
            return EMPTY;
          } else if (vA >= left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.RO_IN_INNER,
              value: [vA as number, right as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      case Operator.O_IN_INNER:
        if (Array.isArray(vB) && vB.length > 1) {
          const [left, right] = vB;
          if (vA >= right) {
            return EMPTY;
          } else if (vA > left && vA < right) {
            return {
              key: expA.key,
              operator: Operator.RO_IN_INNER,
              value: [vA as number, right as number],
            };
          } else {
            return clone(expB);
          }
        } else {
          return EMPTY;
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.RIN) {
    if (!Array.isArray(vA) || vA.length < 2 || !Array.isArray(vB) || vB.length < 2) {
      return EMPTY;
    }

    const [la, ra] = vA;
    const [lb, rb] = vB;

    switch (opB) {
      case Operator.RIN:
        if (ra < lb || la > rb) {
          return EMPTY;
        } else if (la === rb) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: la as number,
          };
        } else if (ra === lb) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: ra as number,
          };
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [la as number, rb as number],
          };
        }
      case Operator.LO_IN_INNER:
        if (ra <= lb || la > rb) {
          return EMPTY;
        }
        if (la === rb) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: la as number,
          };
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [la as number, rb as number],
          };
        }
      case Operator.RO_IN_INNER:
        if (ra < lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb === ra) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: lb as number,
          };
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RO_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      case Operator.O_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la < lb && ra > rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RO_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.LO_IN_INNER) {
    if (!Array.isArray(vA) || vA.length < 2 || !Array.isArray(vB) || vB.length < 2) {
      return EMPTY;
    }

    const [la, ra] = vA;
    const [lb, rb] = vB;

    switch (opB) {
      case Operator.LO_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      case Operator.RO_IN_INNER:
        if (ra < lb || la >= rb) {
          return EMPTY;
        } else if (ra === lb) {
          return {
            key: expA.key,
            operator: Operator.EQ,
            value: ra as number,
          };
        } else if (la === lb && ra === rb) {
          const rst = clone(expA);
          rst.operator = Operator.O_IN_INNER;
          return rst;
        } else if (la < lb && ra > rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.RIN,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.O_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      case Operator.O_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.LO_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.O_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.RO_IN_INNER) {
    if (!Array.isArray(vA) || vA.length < 2 || !Array.isArray(vB) || vB.length < 2) {
      return EMPTY;
    }

    const [la, ra] = vA;
    const [lb, rb] = vB;

    switch (opB) {
      case Operator.RO_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.RO_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RO_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      case Operator.O_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.O_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.RO_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      default:
        return EMPTY;
    }
  } else if (opA === Operator.O_IN_INNER) {
    if (!Array.isArray(vA) || vA.length < 2 || !Array.isArray(vB) || vB.length < 2) {
      return EMPTY;
    }

    const [la, ra] = vA;
    const [lb, rb] = vB;

    switch (opB) {
      case Operator.O_IN_INNER:
        if (ra <= lb || la >= rb) {
          return EMPTY;
        } else if (la <= lb && ra >= rb) {
          return clone(expB);
        } else if (la > lb && ra < rb) {
          return clone(expA);
        } else if (lb > la) {
          return {
            key: expA.key,
            operator: Operator.O_IN_INNER,
            value: [lb as number, ra as number],
          };
        } else {
          return {
            key: expA.key,
            operator: Operator.O_IN_INNER,
            value: [la as number, rb as number],
          };
        }
      default:
        return EMPTY;
    }
  }

  return EMPTY;
}

/**
 * 求两个简单Bool表达式的交集
 */
export function intersectionItem(expA: BoolItem, expB: BoolItem): BoolItem {
  const rst = intersection$(expA, expB);

  if (rst !== null) {
    // makeSureUUID(rst, true);

    // 只有一个区间的Operator.SEGMENTS_INNER
    if (
      rst.operator === Operator.SEGMENTS_INNER &&
      Array.isArray(rst.value) &&
      rst.value.length === 1
    ) {
      return rst.value[0] as BoolItem;
    }

    if (rst.operator === Operator.IN && Array.isArray(rst.value) && rst.value.length === 1) {
      return {
        ...rst,
        operator: Operator.EQ,
        value: rst.value[0] as number,
      };
    }
  }

  return rst;
}

/**
 * 简化一个表达式
 *
 * @param {BoolItem[]} exp - 待简化的表达式，表达式都必须是&&关系
 * @return {BoolItem[]} 简化后的表达式
 */
export function simplifyBoolList(exp: BoolItem[]): BoolItem[] {
  const mapByKey = new Map<string, BoolItem>();

  for (let idx = 0, len = exp.length; idx < len; idx += 1) {
    const item = exp[idx];
    const { key } = item;

    if (mapByKey.has(key)) {
      const prev = mapByKey.get(key) as BoolItem;
      const mergedItem = intersectionItem(prev, item);
      if (mergedItem?.operator === Operator.EMPTY) {
        return [];
      } else {
        mapByKey.set(key, mergedItem);
      }
    } else {
      mapByKey.set(key, item);
    }
  }

  return Array.from(mapByKey.values());
}

/**
 * 将一个Bool表达式展开成多个与关系的表达式
 *
 * @param {Expression} exp - 包含Bool逻辑的表达式
 * @return {Expression[]} 展开的表达式
 */
export function expand(exp: Expression): BoolItem[][] {
  makeSureUUID(exp);
  const itemMap = buildBoolItemMap(exp);
  const validIdTuple = expandImp(exp);
  const rst: BoolItem[][] = validIdTuple
    .map((idList: string[]) =>
      simplifyBoolList(idList.map((id: string) => itemMap.get(id) as BoolItem))
    )
    .filter((list: BoolItem[]) => list.length > 0);

  return rst;
}

function joinBoolItemList(candidate: BoolItem[]): Expression  {
  if (candidate.length === 0) {
    return EMPTY;
  } else if (candidate.length === 1) {
    return candidate[0];
  } else {
    return {
      operator: BoolOperation.AND,
      args: candidate,
    };
  }
}

export function simplify(exp: Expression): Expression {
  if (isBoolExpression(exp)) {
    const itemMatrix = expand(exp);
    if (itemMatrix.length === 0) {
      return EMPTY;
    } else if (itemMatrix.length === 1) {
      return joinBoolItemList(itemMatrix[0]);
    } else {

      // 用两次取反，避免求交集
      return reverse({
        operator: BoolOperation.AND,
        args: itemMatrix
          .map(joinBoolItemList)
          .map((e) => reverse(e))
          .filter((i) => i !== null) as Expression[],
      });
    }
  }

  return exp;
}
